# -*- coding: utf-8 -*-
"""
    xfree.plugins.manager
    ~~~~~~~~~~~~~~~~~~~~~~~
    文件描述：一个用于更轻松地创建数据的模块。
    作者：xfree
    邮箱：cy_83k@qq.com
"""

import collections
import logging
import os

from alembic.util.exc import CommandError
from flask import current_app
from sqlalchemy_utils.functions import create_database, database_exists

from xfree.extensions import db,alembic
from xfree.forum.models import Category, Forum, Post, Topic
from xfree.admin.models import Setting, SettingsGroup
from xfree.user.models import Group, User

logger = logging.getLogger(__name__)


def delete_settings_from_fixture(fixture):
    """从数据库中删除 fixture 中的设置，并返回已删除的设置组和设置。
    :param fixture: 要删除的 fixture。
    :return: 已删除的设置组和设置。
    """
    # 存储已删除的设置
    deleted_settings = {}
    try:
        for settingsgroup in fixture:
            # 根据键查找设置组
            group = SettingsGroup.query.filter_by(key=settingsgroup[0]).first()
            deleted_settings[group] = []

            # 遍历设置组中的设置
            for settings in settingsgroup[1]["settings"]:
                # 根据键查找设置
                setting = Setting.query.filter_by(key=settings[0]).first()
                if setting:
                    # 如果找到设置，将其添加到已删除设置列表中，并从数据库中删除
                    deleted_settings[group].append(setting)
                    setting.delete()

            # 删除设置组
            group.delete()
        return deleted_settings
    except Exception as e:
        logger.error(f"删除fixture设置时出错: {e}")
        return {}

def create_settings_from_fixture(fixture):
    """从 fixture 中插入设置到数据库，并返回已创建的设置组和设置。
    :param fixture: 要插入的 fixture。
    :return: 已创建的设置组和设置。
    """
    # 存储已创建的设置
    created_settings = {}
    try:
        # 遍历 fixture 中的每个设置组
        for settingsgroup in fixture:
            # 根据 fixture 中的信息创建设置组对象
            group = SettingsGroup(
                key=settingsgroup[0],
                name=settingsgroup[1]["name"],
                description=settingsgroup[1]["description"],
            )
            try:
                # 尝试将设置组保存到数据库
                group.save()
                # 在 created_settings 字典中以设置组对象为键，创建一个空列表，用于存储该设置组下的已创建设置
                created_settings[group] = []
                # 遍历设置组中的每个设置
                for settings in settingsgroup[1]["settings"]:
                    # 从 fixture 中获取设置的各个属性，如果不存在则设置为 None 或默认值
                    value = settings[1].get("value")
                    value_type = settings[1].get("value_type")
                    name = settings[1].get("name")
                    description = settings[1].get("description")
                    extra = settings[1].get("extra", "")
                    # 创建设置对象，传入获取到的属性以及设置组的键
                    setting = Setting(
                        key=settings[0],
                        value=value,
                        value_type=value_type,
                        name=name,
                        description=description,
                        extra=extra,
                        settingsgroup=group.key,
                    )
                    if setting:
                        # 如果设置对象有效，将其保存到数据库，并添加到已创建设置列表中
                        setting.save()
                        created_settings[group].append(setting)
            except Exception as inner_e:
                # 如果在保存设置时出现错误，打印错误信息，可用于后续排查问题
                print(f"在组中保存设置时出错 {group.key}: {inner_e}")
        return created_settings
    except Exception as e:
        # 如果在整体处理过程中出现错误，打印错误信息，可用于后续排查问题
        print(f"发生了一个整体错误: {e}")
        return {}


def update_settings_from_fixture(
    fixture, overwrite_group=False, overwrite_setting=False
):
    """从 fixture 中更新数据库中的设置，并返回已更新的设置组和设置。

    :param fixture: 要插入/更新的 fixture。
    :param overwrite_group: 如果为 True，则覆盖已存在的设置组。默认为 False。
    :param overwrite_setting: 如果为 True，则覆盖已存在的设置。默认为 False。
    """
    # 默认字典，用于存储更新后的设置
    updated_settings = collections.defaultdict(list)

    try:
        # 遍历 fixture 中的每个设置组
        for settingsgroup in fixture:
            # 根据键查找设置组
            group_key = settingsgroup[0]
            existing_group = SettingsGroup.query.filter_by(key=group_key).first()

            # 判断是否需要更新或创建设置组
            if (existing_group is not None and overwrite_group) or existing_group is None:
                if existing_group is not None:
                    # 如果设置组存在，更新其属性
                    existing_group.name = settingsgroup[1]["name"]
                    existing_group.description = settingsgroup[1]["description"]
                else:
                    # 如果设置组不存在，创建新的设置组对象
                    new_group = SettingsGroup(
                        key=group_key,
                        name=settingsgroup[1]["name"],
                        description=settingsgroup[1]["description"],
                    )
                    existing_group = new_group

                try:
                    # 保存设置组
                    existing_group.save()
                except Exception as group_save_error:
                    # 处理设置组保存错误
                    print(f"使用密钥保存设置组时出错 {group_key}: {group_save_error}")
                    continue

            # 将更新后的设置组添加到结果字典中
            updated_settings[existing_group] = []

            # 遍历设置组中的设置
            for settings in settingsgroup[1]["settings"]:
                # 根据键查找设置
                setting_key = settings[0]
                existing_setting = Setting.query.filter_by(key=setting_key).first()

                # 如果设置存在，则检查其是否与 fixture 中的设置不同
                if existing_setting is not None:
                    is_different = (
                        existing_setting.value!= settings[1]["value"]
                        or existing_setting.value_type!= settings[1]["value_type"]
                        or existing_setting.name!= settings[1]["name"]
                        or existing_setting.description!= settings[1]["description"]
                        or existing_setting.extra!= settings[1].get("extra", "")
                        or existing_setting.settingsgroup!= existing_group.key
                    )
                else:
                    is_different = True

                # 判断是否需要更新或创建设置
                if (existing_setting is not None and overwrite_setting and is_different) or existing_setting is None:
                    if existing_setting is not None:
                        # 如果设置存在，更新其属性
                        existing_setting.value = settings[1]["value"]
                        existing_setting.value_type = settings[1]["value_type"]
                        existing_setting.name = settings[1]["name"]
                        existing_setting.description = settings[1]["description"]
                        existing_setting.extra = settings[1].get("extra", "")
                        existing_setting.settingsgroup = existing_group.key
                    else:
                        # 如果设置不存在，创建新的设置对象
                        new_setting = Setting(
                            key=setting_key,
                            value=settings[1]["value"],
                            value_type=settings[1]["value_type"],
                            name=settings[1]["name"],
                            description=settings[1]["description"],
                            extra=settings[1].get("extra", ""),
                            settingsgroup=existing_group.key,
                        )
                        existing_setting = new_setting

                    try:
                        # 保存设置，并将其添加到已更新设置列表中
                        existing_setting.save()
                        updated_settings[existing_group].append(existing_setting)
                    except Exception as setting_save_error:
                        # 处理设置保存错误
                        print(f"使用密钥保存设置时出错 {setting_key}: {setting_save_error}")
    except Exception as overall_error:
        # 处理整体过程中的错误
        print(f"发生了一个整体错误: {overall_error}")
        return collections.defaultdict(list)

    return updated_settings


def create_default_settings():
    """创建默认设置。
    ：：return：已更新设置的字典。
    """
    from xfree.fixtures.settings import fixture

    create_settings_from_fixture(fixture)


def create_default_groups():
    """创建默认的五个用户组。"""
    from xfree.fixtures.groups import fixture

    result = []
    for key, value in fixture.items():
        # 创建用户组对象
        group = Group(name=key)

        # 为用户组设置属性
        for k, v in value.items():
            setattr(group, k, v)

        # 保存用户组
        group.save()
        result.append(group)
    return result


def create_user(username, password, email, groupname):
    """创建一个用户。

    :param username: 用户的用户名。
    :param password: 用户的密码。
    :param email: 用户的电子邮件地址。
    :param groupname: 用户所属的组名。
    """
    if groupname == "member":
        # 获取成员组
        group = Group.get_member_group()
    else:
        # 根据组名属性查找用户组
        group = Group.query.filter(getattr(Group, groupname) == True).first()

    # 创建用户对象并返回
    user = User.create(
        username=username,
        password=password,
        email=email,
        primary_group_id=group.id,
        activated=True,
    )
    return user


def update_user(username, password, email, groupname):
    """更新一个现有用户。

    :param username: 用户的用户名。
    :param password: 用户的密码。
    :param email: 用户的电子邮件地址。
    :param groupname: 用户所属的组名。
    """
    # 根据用户名查找用户
    user = User.query.filter_by(username=username).first()
    if user is None:
        return None

    if groupname == "member":
        # 获取成员组
        group = Group.get_member_group()
    else:
        # 根据组名属性查找用户组
        group = Group.query.filter(getattr(Group, groupname) == True).first()

    # 更新用户信息并保存
    user.password = password
    user.email = email
    user.primary_group = group
    return user.save()


def create_welcome_forum():
    """创建“欢迎论坛”，包括分类、论坛、主题和帖子。如果没有用户则返回 False。"""
    if User.query.count() < 1:
        return False

    # 获取第一个用户
    user = User.query.filter_by(id=1).first()

    # 创建分类
    category = Category(title="我的分类", position=1)
    category.save()

    # 创建论坛
    forum = Forum(
        title="欢迎光临", description="您的第一个论坛！", category_id=category.id
    )
    forum.save()

    # 创建主题和帖子
    topic = Topic(title="欢迎光临！")
    post = Post(content="与您的新 xfree 论坛玩得开心！")

    # 保存主题和帖子
    topic.save(user=user, forum=forum, post=post)
    return True


def create_test_data(users=5, categories=2, forums=2, topics=1, posts=1):
    """创建测试数据，包括用户、分类、论坛、主题和帖子。

    :param users: 用户数量。
    :param categories: 分类数量。
    :param forums: 每个分类中的论坛数量。
    :param topics: 每个论坛中的主题数量。
    :param posts: 每个主题中的帖子数量。
    """
    # 创建默认用户组和设置
    create_default_groups()
    create_default_settings()

    # 存储创建的数据数量
    data_created = {"users": 0, "categories": 0, "forums": 0, "topics": 0, "posts": 0}

    # 创建指定数量的用户
    for u in range(1, users + 1):
        username = "test%s" % u
        email = "test%s@example.org" % u
        user = User(username=username, password="test", email=email)
        user.primary_group_id = u
        user.activated = True
        user.save()
        data_created["users"] += 1

    user1 = User.query.filter_by(id=1).first()
    user2 = User.query.filter_by(id=2).first()

    # 创建指定数量的分类
    with db.session.no_autoflush:
        for i in range(1, categories + 1):
            category_title = "Test Category %s" % i
            category = Category(title=category_title, description="Test Description")
            category.save()
            data_created["categories"] += 1

            # 在每个分类中创建指定数量的论坛
            for j in range(1, forums + 1):
                if i == 2:
                    j += 2

                forum_title = "Test Forum %s %s" % (j, i)
                forum = Forum(
                    title=forum_title, description="Test Description", category_id=i
                )
                forum.save()
                data_created["forums"] += 1

                # 在每个论坛中创建指定数量的主题
                for _ in range(1, topics + 1):
                    # 创建主题
                    topic = Topic(title="Test Title %s" % j)
                    post = Post(content="Test Content")
                    topic.save(post=post, user=user1, forum=forum)
                    data_created["topics"] += 1

                    # 在每个主题中创建指定数量的帖子
                    for _ in range(1, posts + 1):
                        # 创建帖子
                        post = Post(content="Test Post")
                        # db.session.add_all([post, user2, topic])
                        post.save(user=user2, topic=topic)
                        data_created["posts"] += 1

    return data_created


def insert_bulk_data(topic_count=10, post_count=100):
    """在第一个论坛中创建指定数量的主题，每个主题包含指定数量的帖子。

    :param topic_count: 主题数量。
    :param post_count: 每个主题中的帖子数量。
    """
    try:
        # 查询第一个用户和第二个用户以及第一个论坛
        user1 = User.query.filter_by(id=1).first()
        user2 = User.query.filter_by(id=2).first()
        forum = Forum.query.filter_by(id=1).first()

        if not (user1 or user2 or forum):
            return False

        last_post = Post.query.order_by(Post.id.desc()).first()
        last_post_id = 1 if last_post is None else last_post.id

        created_posts = 0
        created_topics = 0
        posts = []

        with db.session.no_autoflush:
            for i in range(1, topic_count + 1):
                last_post_id += 1

                # 创建主题，并同时创建第一个帖子
                topic = Topic(title=f"测试标题 {i}")
                first_post = Post(content="First Post")
                topic.save(post=first_post, user=user1, forum=forum)
                created_topics += 1

                # 在每个主题中创建指定数量的帖子
                for _ in range(1, post_count + 1):
                    last_post_id += 1
                    post = Post(content="Some other Post", user=user2, topic=topic.id)
                    topic.last_updated = post.date_created
                    topic.post_count += 1

                    created_posts += 1
                    posts.append(post)

            # 批量保存所有创建的帖子
            db.session.bulk_save_objects(posts)

        # 更新相关统计信息
        forum.recalculate(last_post=True)
        user1.recalculate()
        user2.recalculate()

        return created_topics, created_posts
    except Exception as e:
        # 处理异常情况，可以记录错误日志等
        print(f"发生错误: {e}")
        return False


def create_latest_db(target="default@head"):
    """创建数据库，包括使用 SQLAlchemy 的 db.create_all 方法创建数据库模式，并设置修订版本为“head”。
    :param target: 目标分支。默认为“default@head”。
    ：return: 无返回值。
    """
    try:
        # 检查数据库是否存在
        if not database_exists(db.engine.url):
            # 如果数据库不存在，则创建数据库
            create_database(db.engine.url)

        # 创建数据库表结构
        db.create_all()

        # 使用Alembic迁移最新版数据
        alembic.stamp(target=target)
    except Exception as e:
        # 处理异常情况，可以记录错误日志等
        print(f"创建数据库时出错: {e}")
        exit()


def has_migrations(plugin):
    """检查插件是否有迁移文件。

    :param plugin: 插件对象。
    """
    try:
        migrations_path = os.path.join(plugin.__path__[0], "migrations")
        if os.path.exists(migrations_path):
            files = os.listdir(migrations_path)
            if len(files) > 0:
                return True
        return False
    except Exception as e:
        # 处理异常情况，可以记录错误日志等
        print(f"检查插件迁移时出错： {e}")
        return False


def run_plugin_migrations(plugins=None):
    """运行插件的迁移。

    :param plugins: 插件列表。如果为 None，则运行所有外部插件的迁移。
    """
    if plugins is None:
        plugins = current_app.pluggy.get_external_plugins()

    for plugin in plugins:
        plugin_name = current_app.pluggy.get_name(plugin)

        if not has_migrations(plugin):
            logger.debug("没有找到插件 %s 的迁移文件" % plugin_name)
            continue

        try:
            alembic.upgrade(target="{}@head".format(plugin_name))
        except CommandError as exc:
            logger.debug(
                "无法为插件 {} 运行迁移，因为出现以下异常：".format(plugin_name),
                exc_info=exc,
            )